/*
 * Copyright 2008-2009 Aying.Net with Fuchun.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.aying.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.aying.resources.Resources;

/**
 * <code>Crypto</code> 是一个抽象类，它为字符串、文本文件等介质提供了一组加密解密的方法。
 * <p>
 * <code>Crypto</code> 提供了一个类方法 <code>getInstance</code>，以获得一个此类型的通用对象。
 * <code>Crypto</code> 的 <code>getInstance</code> 方法返回一个 <code>Crypto</code>
 * 对象。
 * </p>
 * 
 * @author Fuchun
 * @version 1.0, 2009-6-12
 */
public abstract class Crypto {

    @SuppressWarnings("unused")
    private static final Log log = LogFactory.getLog(Crypto.class);

    /** <code>DES</code> 算法的名称。 */
    public static final String ALGORITHM_DES = "DES";

    /** <code>MD5</code> 算法的名称。 */
    public static final String ALGORITHM_MD5 = "MD5";

    /** <code>SHA</code> 算法的名称。 */
    public static final String ALGORITHM_SHA = "SHA-1";
    
    /** 加密文本文件时，备份的文件默认扩展名。*/
    public static final String BACKUP_FILE_EXTENSION = "bak";

    /**
     * 默认的 <code>KeySpec</code> 的密钥内容(该字符串的前8个字节将作为密钥的内容)的Key。
     */
    protected static final String DEFAULT_KEY_SPEC_STRING = "util.crypto.defaultSecretKey";

    /* 要实现的算法名称 */
    private String algorithm;

    /** 加密(解密)的密钥。 */
    protected SecretKeySpec secretKeySpec;

    /**
     * 默认的构造方法，初始化一个新的 <code>Crypto</code> 对象。
     */
    protected Crypto() {}

    /**
     * 获得一个默认密钥的基于 <code>DES</code> 算法的 <code>Crypto</code> 新实例。
     * 
     * @return 默认密钥的基于 <code>DES</code> 算法的 <code>Crypto</code> 新实例。
     */
    public static Crypto getInstance() {
        return createCrypto(Resources.getMessage(DEFAULT_KEY_SPEC_STRING));
    }

    /**
     * 获得一个给定密钥的基于 <code>DES</code> 算法的 <code>Crypto</code> 新实例。
     * 
     * @param sKeySpec 给定的密钥字符串。
     * @return 指定密钥的基于 <code>DES</code> 算法的 <code>Crypto</code> 新实例。
     */
    public static Crypto getInstance(String sKeySpec) {
        return createCrypto(sKeySpec);
    }

    /**
     * 获得一个给定密钥的基于 <tt>algorithm</tt> 算法的 <code>Crypto</code> 新实例。
     * <p>
     * 若 <code>Crypto</code> 不支持给定的 <tt>algorithm</tt> 算法，则返回
     * <code>NULL</code>。
     * </p>
     * 
     * @param sKeySpec 给定的密钥字符串。
     * @param algorithm 给定的算法名称。
     * @return 指定密钥和算法的 <code>Crypto</code> 新实例。
     */
    public static Crypto getInstance(String sKeySpec, String algorithm) {
        try {
            return createCrypto(sKeySpec, algorithm);
        } catch (NoSuchAlgorithmException ex) {
            log.warn(ex.getMessage());
            return null;
        }
    }

    private static Crypto createCrypto(String sKeySpec) {
        try {
            return createCrypto(sKeySpec, ALGORITHM_DES);
        } catch (NoSuchAlgorithmException ex) {
            log.warn(ex.getMessage());
            return null;
        }
    }

    private static Crypto createCrypto(String sKeySpec, String algorithm)
            throws NoSuchAlgorithmException {
        if (sKeySpec == null || sKeySpec.trim().length() == 0)
            sKeySpec = Resources.getMessage(DEFAULT_KEY_SPEC_STRING);
        if (!ALGORITHM_DES.equalsIgnoreCase(algorithm)
                && !ALGORITHM_MD5.equalsIgnoreCase(algorithm)
                && !ALGORITHM_SHA.equalsIgnoreCase(algorithm)) {
            throw new NoSuchAlgorithmException(Resources.getMessage(
                    Resources.UTIL_CRYPTO_NO_ALGORITHM, algorithm));
        }
        if (ALGORITHM_DES.equalsIgnoreCase(algorithm)) {
            return new DESCrypto(sKeySpec);
        }
        return null;
    }

    /**
     * 对给定的字符串进行加密处理，返回 {@link #getAlgorithm()} 加密(解密)算法加密过的字符串。
     * <p>
     * 若当前环境不支持特定的加密(解密)算法，则返回 <code>NULL</code>。
     * </p>
     * 
     * @param plaintext 要加密的字符串。
     * @return 给定字符串的加密密文。
     */
    public abstract String encrypt(String plaintext);

    /**
     * 对给定的字符串进行解密处理，返回 {@link #getAlgorithm()} 加密(解密)算法解密的字符串。
     * <p>
     * 若当前的加密(解密)算法和给定的经加密的 <tt>encrypttext</tt> 字符串的加密密钥不一致，则 无法进行解密，返回
     * <code>NULL</code>。
     * </p>
     * 
     * @param encrypttext 要解密的密文字符串。
     * @return 给定的密文字符串的原文字符串。若解密失败，则返回 <code>NULL</code>。
     */
    public abstract String decrypt(String encrypttext);

    public File encrypt(String filePath, String encryptFilePath) throws FileNotFoundException, IOException {
        File txtFile = new File(filePath);
        if(!txtFile.exists())
            throw new FileNotFoundException(Resources.getMessage(Resources.UTIL_ERROR_ENCRYPT_FILE_NOTFOUND));
        return encryptFile(txtFile, encryptFilePath);
    }

    public File encrypt(String filePath, String encryptFilePath, boolean isBackup)
            throws FileNotFoundException {
        File txtFile = new File(filePath);
        if(!txtFile.exists())
            throw new FileNotFoundException(Resources.getMessage(Resources.UTIL_ERROR_ENCRYPT_FILE_NOTFOUND));
        // 备份要加密的文本文件
        File backupTxtFile = null;
        if(isBackup) {
            backupTxtFile = new File(txtFile.getAbsolutePath().concat(".").concat(BACKUP_FILE_EXTENSION));
        }
        return null;
    }

    public File decrypt(String encryptFilePath, String filePath) throws FileNotFoundException {

        return null;
    }

    public File decrypt(String encryptFilePath, String filePath, boolean isBackup)
            throws FileNotFoundException {

        return null;
    }
    
    protected abstract File encryptFile(File txtFile, String encryptFilePath) throws IOException;
    
    protected abstract File decryptFile(File encryptFilePath, String filePath) throws IOException;

    /**
     * 获取算法名称。
     * 
     * @return 算法名称。
     */
    public String getAlgorithm() {
        return algorithm;
    }

    /**
     * 设置算法名称。
     * 
     * @param algorithm 要设置的 算法名称。
     */
    public void setAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    /**
     * 获取加密(解密)的密钥。
     * 
     * @return 加密(解密)的密钥。
     */
    public SecretKeySpec getSecretKeySpec() {
        return secretKeySpec;
    }

    /**
     * 设置加密(解密)的密钥。
     * 
     * @param secretKeySpec 要设置的 加密(解密)的密钥。
     */
    public void setSecretKeySpec(SecretKeySpec secretKeySpec) {
        this.secretKeySpec = secretKeySpec;
    }

    public static void main(String[] args) {
        Crypto crypto1 = Crypto.getInstance("我们都是中国人！");
        Crypto crypto2 = Crypto.getInstance("我们都是中国人！");
        String text = "1";
        String encryptText = crypto1.encrypt(text);
        String decryptText = crypto2.decrypt(encryptText);
        System.out.println("原文字：" + text);
        System.out.println("加密后：" + encryptText);
        System.out.println("解密后：" + decryptText);
    }
}
